package cn.edu.pku.sei.ra.selfadapt;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EModelElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
import org.eclipse.ocl.ParserException;
import org.eclipse.ocl.ecore.OCL;
import org.eclipse.ocl.ecore.OCLExpression;


public class OCLAnnoExec {
	
	public static String ANNO_SOURCE_PREFIX="http://www.sei.pku.edu.cn/rsa/oclpolicy";
	
	ResourceSet resourceSet=null;
	URI modelURI=null;
	OCL ocl=null;
	OCL.Helper helper=null;
	
	public OCLAnnoExec(EPackage metaPack, String modelPath){
		resourceSet=new ResourceSetImpl();
		
		resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put
				(Resource.Factory.Registry.DEFAULT_EXTENSION, 
				new XMIResourceFactoryImpl());
		
		resourceSet.getPackageRegistry().put(
				EcorePackage.eNS_URI, 
				EcorePackage.eINSTANCE);
		
		resourceSet.getPackageRegistry().put(
				metaPack.getNsURI(), metaPack);
		
		modelURI=URI.createFileURI(modelPath);		
		
		ocl=OCL.newInstance();
		helper=ocl.createOCLHelper();
	}
	
	public Resource execute() throws OCLExtSyntaxException, ParserException, IOException{
		Resource res=resourceSet.getResource(modelURI, true);
		res.unload();
		res.load(Collections.EMPTY_MAP);
		for(TreeIterator<EObject> it=res.getAllContents();it.hasNext();){
			EObject obj=it.next();
			if(obj instanceof EModelElement){
				runOnElement((EModelElement)obj);
			}
		}
		return res;
	}
	
	public Object evaluate(EObject obj,String oclText) throws ParserException{
		
		helper.setContext(obj.eClass());
		OCLExpression expr=helper.createQuery(oclText);
		OCL.Query query=ocl.createQuery(expr);
		return query.evaluate(obj);
		
	}
	
	public Object evaluateExt(EObject self, List<EObject> refs,String oclExtText) throws OCLExtSyntaxException, ParserException{
		int begin=0;
		int end=0;
		int ref=-1;
		while(true){
			begin=oclExtText.indexOf("${",begin);
			if(begin<0)
				break;
			end=oclExtText.indexOf("}", begin);
			String number=oclExtText.substring(begin+2,end);
			int tmp=Integer.valueOf(number).intValue();
			if(ref==-1)
				ref=tmp;
			else if(ref!=tmp)
				throw(new OCLExtSyntaxException("two reference numbers cannot appear in one sentence"));
			begin=end;
		}
		if(ref==-1)
			return evaluate(self,oclExtText);
		else{
			String oclText=oclExtText.replace("${"+String.valueOf(ref)+"}", "self");
			return evaluate(refs.get(ref),oclText);
		}
	}
	
	public void runOnElement(EModelElement elem) throws OCLExtSyntaxException, ParserException{
		List<EAnnotation> annos=elem.getEAnnotations();
		for(EAnnotation anno:annos){
			if(!anno.getSource().startsWith(ANNO_SOURCE_PREFIX))
				continue;
			List<EObject>references=anno.getReferences();
			String condition=anno.getDetails().get("on");
			Boolean res=(Boolean)evaluate(elem,condition);
			if(!res.booleanValue())
				continue;
			String[] actions=anno.getDetails().get("do").split(";");
			for(int i=0;i<actions.length;i++){
				if(actions[i].trim().length()<1)
					continue;
				String[] parts=actions[i].split("<-");
				if(parts.length==1){
					evaluateExt(elem,references,parts[0]);
					continue;
				}
				int dot=parts[0].lastIndexOf(".");
				EObject toSet=(EObject)evaluateExt(elem,references,parts[0].substring(0, dot));
				String featureName=parts[0].substring(dot+1);
				Object value=evaluateExt(elem,references,parts[1]);
				toSet.eSet(toSet.eClass().getEStructuralFeature(featureName), value);
			}
		}
	}
	
	public class OCLExtSyntaxException extends Exception{
		public OCLExtSyntaxException(String s){
			super(s);
		}
		public OCLExtSyntaxException(Exception e){
			super(e);
		}
	}
}
